#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <GL/glut.h>
#include "Camera.hpp"
#include "Frustum.hpp"
#include "objreader.h"
#include "HW23.h"
using namespace std;

int Window::width = 800;
int Window::height = 800;
int Window::radius = min(Window::height, Window::width)/2;
int mouseTargetX;
int mouseTargetY;
int mouseTargetZ;
double boundXmin, boundXmax, boundYmin, boundYmax, boundZmin, boundZmax;
Camera camera1 = Camera(Vector3(0,0,2/*2*sqrt((float)3)/2*/), Vector3(0,0,0), Vector3(0,1,0));  //camera dist should be near*sqrt(3)/2 or near*tan(BasicMath::radian(60)/2);
static HW2 hw2;

int nVerts;
float *vertices;
float *normals;
float *texcoords;
int nIndices;
int *indices;


//----------------------------------------------------------------------------
// Callback method called when system is idle.
void Window::idleCallback(void)
{
  //cube.spin(.25);       // rotate cube by 1 degree
  //displayCallback();    // call display routine to redraw cube
}

//----------------------------------------------------------------------------
// Callback method called when window is resized.
void Window::reshapeCallback(int w, int h)
{
  width = w;
  height = h;
  glViewport(0, 0, w, h);  // set new viewport size
  //glMatrixMode(GL_PROJECTION);
  //glLoadIdentity();
  //glFrustum(-10.0, 10.0, -10.0, 10.0, 10, 1000.0); // set perspective projection viewing frustum
  //glTranslatef(0, 0, -20);
  //glMatrixMode(GL_MODELVIEW);
  Frustum::genPpersp(60, 1, 1, 3);
  Frustum::setGlProj();
}

//----------------------------------------------------------------------------
// Callback method called when window readraw is necessary or
// when glutPostRedisplay() was called.
void Window::displayCallback(void)
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // clear color and depth buffers
  //glLoadIdentity();
  glMatrixMode(GL_MODELVIEW);
  //glPopMatrix();
  glLoadIdentity();
  hw2.translation();
  hw2.scale();
  glLoadMatrixd((camera1.getInverse().mult(hw2.getMatrix())).getPointer());
  //glLoadMatrixd((hw2.getMatrix().mult(camera1.getInverse())).getPointer());
    //glLoadMatrixd(hw2.getMatrix().getPointer());
  //glPushMatrix();

    //glLoadMatrixd(camera1.getInverse().getPointer());

  //glLoadMatrixd(cube.getMatrix(1).getPointer());
  //createSphere(20,0);
  //glLoadMatrixd(cube.getMatrix(2).getPointer());
  //createSphere(20,2);
  //glLoadMatrixd(cube.getMatrix(3).getPointer());
  //createSphere(20,3);
  HW2::displayObject();

  glBegin(GL_QUADS);
	glColor3f(1,0,0);
	//glVertex3f();
  glEnd;
	  
  
  //glLoadIdentity();
  
  //glLoadMatrixd(camera1.getInverse().getPointer());
 // glBegin(GL_QUADS);
	//glColor3f(0,1,0);
	//glVertex3f(-1, 1, 0);
	//glVertex3f(-1, -1, 0);
	//glVertex3f(1, -1, 0);
	//glVertex3f(1, 1, 0);
	//glColor3f(1,0,0);
	//glVertex3f(-.5, .4, .1);
	//glVertex3f(-.5, -.4, 0.1);
	//glVertex3f(.5, -.4, 0.1);
	//glVertex3f(.5, .4, 0.1);
 // glEnd;

  glFlush();  
  glutSwapBuffers();
}

void HW2::displayObject(){
	glBegin(GL_TRIANGLES);
	cout<<"started"<<endl;
	int i, j;

	glColor3f(0,0,1);
	for (i=0; i<nIndices*3; i+=3) {
		if (i%9==0) glColor3f((i+1)/((double)nIndices*3), (i+1)/((double)nIndices*3), (nIndices*3 - i+1)/((double)nIndices*3));
		glVertex3f(vertices[i], vertices[i+1], vertices[i+2]);
		//cout<<i<<" drew a triangle: "<< vertices[i]<< " " << vertices[i+1]<< " " << vertices[i+2] << endl;
	}

	glEnd();
}
void HW2::translation() {
//find center by taking average of bounding points
double x, y, z;
x = (boundXmax+boundXmin)/2.0;
y = (boundYmax+boundYmin)/2.0;
z = (boundZmax+boundZmin)/2.0;
cout << "neg: " << x<< " " << y << " " << z << endl;
Matrix4 transMatrix;
transMatrix.translate(-x, -y, -z);
hw2.matrix = transMatrix.mult(hw2.matrix);
cout << "translation: " << scale << endl;
  cout << "boundXmin: " <<boundXmin-x<< endl;
  cout << "boundXmax: " <<boundXmax-x<< endl; 
  cout << "boundYmin: " <<boundYmin-y<< endl; 
  cout << "boundYmax: " <<boundYmax-y<< endl; 
  cout << "boundZmin: " <<boundZmin-z<< endl; 
  cout << "boundZmax: " <<boundZmax-z<< endl;
}

void HW2::scale() {
double scale = 2.0/max(boundXmax-boundXmin, max(boundYmax-boundYmin, boundZmax-boundZmin));
Matrix4 scaleMatrix;
scaleMatrix.scale(scale, scale, scale);
hw2.matrix = scaleMatrix.mult(hw2.matrix);
cout << "scale: " << scale << endl;
  cout << "boundXmin: " <<boundXmin*scale<< endl;
  cout << "boundXmax: " <<boundXmax*scale<< endl; 
  cout << "boundYmin: " <<boundYmin*scale<< endl; 
  cout << "boundYmax: " <<boundYmax*scale<< endl; 
  cout << "boundZmin: " <<boundZmin*scale<< endl; 
  cout << "boundZmax: " <<boundZmax*scale<< endl;

}

Matrix4& HW2::getMatrix()
{
  return matrix;
}
void HW2::mouseClick(int button, int state, int x, int y) {

	x = x-Window::width/2;
	y = -(y-Window::width/2);
	if (x*x+y*y < Window::radius*Window::radius) {
		mouseTargetX = x;
		mouseTargetY = y;
		mouseTargetZ = sqrt((float)(Window::radius*Window::radius-(mouseTargetX*mouseTargetX + mouseTargetY*mouseTargetY)));
	}
}

void HW2::mouseMotion(int x, int y) {

	x = x-Window::width/2;
	y = -(y-Window::height/2);
	if (x*x+y*y < Window::radius*Window::radius) {
		int z = sqrt((float)(Window::radius*Window::radius-(mouseTargetX*mouseTargetX + mouseTargetY*mouseTargetY)));
		if (x!=0 && y!=0 && z!=0) {
			Vector3 vector1 = Vector3(mouseTargetX, mouseTargetY, mouseTargetZ);
			Vector3 vector2 = Vector3(x,y,z);
			Vector3 vector3 = vector1.cross(vector2);
			if (vector1.length() != 0 && vector2.length()!=0 && vector3.length()!=0){
				double angle = BasicMath::degree(asin(vector3.length()/(vector1.length()*vector2.length())));
				Matrix4 rotationMatrix, transMatrix, invTransMatrix;
				rotationMatrix.rotateArbitrary(vector3, angle);
				transMatrix.translate(camera1.getd().getx(), camera1.getd().gety(), camera1.getd().getz());
				invTransMatrix.translate(-camera1.getd().getx(), -camera1.getd().gety(), -camera1.getd().getz());
				hw2.matrix = invTransMatrix.mult(rotationMatrix.mult(transMatrix.mult(hw2.matrix)));

				mouseTargetX = x;
				mouseTargetY = y;
				mouseTargetZ = z;
				//cout<<"reached"<<endl;	
			    glutPostRedisplay();
			}
		}
	}

}
//main stolen from cube for now

int main(int argc, char *argv[])
{
  //float specular[]  = {1.0, 1.0, 1.0, 1.0};
  //float shininess[] = {100.0};
  //float position[]  = {0.0, 10.0, 1.0, 0.0};	// lightsource position
  
  glutInit(&argc, argv);      	      	      // initialize GLUT
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);   // open an OpenGL context with double buffering, RGB colors, and depth buffering
  glutInitWindowSize(Window::width, Window::height);      // set initial window size
  glutCreateWindow("OpenGL Cube");    	      // open window and set window title
  if (glutGet(GLUT_WINDOW_COLORMAP_SIZE) != 0) 
  {
    cerr << "Fatal Error: bad RGBA colormap size\n" << endl;
    exit(1);
  }
  glEnable(GL_DEPTH_TEST);            	      // enable depth buffering
  glClear(GL_DEPTH_BUFFER_BIT);       	      // clear depth buffer
  glClearColor(0.0, 0.0, 0.0, 0.0);   	      // set clear color to black
  //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);  // set polygon drawing mode to fill front and back of each polygon
  glShadeModel(GL_SMOOTH);             	      // set shading to smooth
  glMatrixMode(GL_PROJECTION); 
  glPushMatrix();
  
  // Generate material properties:
  //glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);
  //glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
  //glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
  //glEnable(GL_COLOR_MATERIAL);
  
  // Generate light source:
  //glLightfv(GL_LIGHT0, GL_POSITION, position);
  //glEnable(GL_LIGHTING);
  //glEnable(GL_LIGHT0);
  
  // Install callback functions:
  glutDisplayFunc(Window::displayCallback);
  glutReshapeFunc(Window::reshapeCallback);
  glutIdleFunc(Window::idleCallback);
    
  // Initialize cube matrix:
  //cube.getMatrix(1).identity();
  //cube.getMatrix(2).identity();
  //cube.getMatrix(3).identity();

  ObjReader::readObj("dragon_smooth.obj", nVerts, &vertices, &normals, &texcoords, nIndices, &indices);
  cout << nVerts << endl;
  cout << nIndices << endl;
  int i,j;
  i=0;
  boundXmin = vertices[0];
  boundXmax = vertices[0];
  boundYmin = vertices[1];
  boundYmax = vertices[1];
  boundZmin = vertices[2];
  boundZmax = vertices[2];
  for (i=0; i<nIndices*3; i+=3) {
	//if (i%9==0) glColor3f((i+1)/((double)nIndices*3), (i+1)/((double)nIndices*3), (nIndices*3 - i+1)/((double)nIndices*3));
	//glVertex3f(vertices[i], vertices[i+1], vertices[i+2]);
	//cout<<i;
	//cout<<" drew a texcoord: "<< vertices[i]<< " " << vertices[i+1]<< " " << vertices[i+2] << endl;
	//cin>>j;
	  
	  boundXmin = std::min(boundXmin, (double)vertices[i]);
	  boundYmin = std::min(boundYmin, (double)vertices[i+1]);
	  boundZmin = std::min(boundZmin, (double)vertices[i+2]);
	  boundXmax = std::max(boundXmax, (double)vertices[i]);
	  boundYmax = std::max(boundYmax, (double)vertices[i+1]);
	  boundZmax = std::max(boundZmax, (double)vertices[i+2]);
  }

  cout << "boundXmin: " <<boundXmin<< endl;
  cout << "boundXmax: " <<boundXmax<< endl; 
  cout << "boundYmin: " <<boundYmin<< endl; 
  cout << "boundYmax: " <<boundYmax<< endl; 
  cout << "boundZmin: " <<boundZmin<< endl; 
  cout << "boundZmax: " <<boundZmax<< endl;

  //glutMouseFunc(HW2::mouseClick);
  //glutMotionFunc(HW2::mouseMotion);


  hw2.getMatrix().identity();

  glutMainLoop();
  return 0;
}